/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.manager;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.openislands.oi.config.MyNodeInfo;
import org.openislands.oi.constant.MessageCodeEnum;
import org.openislands.oi.constant.NodeState;
import org.openislands.oi.dao.entity.NodeDO;
import org.openislands.oi.dao.mapper.NodeMapper;
import org.openislands.oi.error.LogicException;
import org.openislands.oi.manager.union.UnionPipelineRegister;
import org.openislands.oi.manager.union.UnionPipelineRegisterEvent;
import org.openislands.oi.pojo.base.Generic;
import org.openislands.oi.pojo.dto.CommonResult;
import org.openislands.oi.pojo.dto.NodeDTO;
import org.openislands.oi.pojo.dto.NodePageDTO;
import org.openislands.oi.pojo.dto.RouteDTO;
import org.openislands.oi.scheduling.StateEngine;
import org.openislands.oi.util.ValueCopyUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Service
@SuppressWarnings("unused")
public class CommonNodeManager implements UnionPipelineRegisterEvent {
    public static final Generic<NodeDTO> DELETE_NODE_AND_ROUTE = new Generic<>();
    public static final Generic<NodeDTO> ADD_NODE_AND_ROUTE = new Generic<>();
    public static final Generic<NodeDTO> UPDATE_NODE_AND_ROUTE = new Generic<>();

    private final GeneralManager<NodeMapper, NodeDO> nodeManager;

    @Resource
    private CommonRouteManager commonRouteManager;

    @Resource
    private StateEngine stateEngine;

    @Resource(type = MyNodeInfo.class)
    private MyNodeInfo myNodeInfo;

    public CommonNodeManager(NodeMapper nodeMapper) {
        this.nodeManager = new GeneralManager<>(nodeMapper);
    }

    public GeneralManager<NodeMapper, NodeDO> getNodeManager() {
        return this.nodeManager;
    }

    public String myNodeId() {
        return myNodeInfo.getNodeId();
    }

    public Boolean myNodeId(String nodeId) {
        return myNodeInfo.getNodeId().equals(nodeId);
    }

    public NodeDTO myNodeDTO() {
        return ValueCopyUtils.copyProperties(this.myNodeInfo, new NodeDTO(), (info, dto) -> {
            final RouteDTO routeDTO = new RouteDTO();
            routeDTO.setHttpAccess(info.getHttpAccess());
            routeDTO.setHttpsAccess(info.getHttpsAccess());
            routeDTO.setGrpcAccess(info.getGrpcAccess());
            routeDTO.setGrpcsAccess(info.getGrpcsAccess());
            dto.setRouteDTOList(Collections.singletonList(routeDTO));
            return dto;
        });
    }

    public Boolean addNode(NodeDTO nodeDTO) {
        try {
            // insert party info
            return nodeManager.save(nodeManager.copy(nodeDTO));
        } catch (DuplicateKeyException e) {
            throw new LogicException(e, MessageCodeEnum.DUPLICATE_KEY_ERROR, e.getCause().getMessage());
        } catch (Exception e) {
            throw new LogicException(e, MessageCodeEnum.DATA_UPDATE_ERROR, e.getCause() != null ? e.getCause().getMessage() : e.getMessage());
        }
    }

    public NodeDTO deleteByNodeId(NodeDTO nodeDTO) {
        boolean nr = nodeManager.delete(uw -> uw.eq(NodeDO::getNodeId, nodeDTO.getNodeId()));
        LogicException.isTrue(nr, MessageCodeEnum.DATA_UPDATE_ERROR, String.format("delete node %s error", nodeDTO.getNodeId()));
        return nodeDTO;
    }

    @Transactional
    public Boolean updateNodeInfo(NodeDTO nodeDTO) {
        final NodeDO nodePartyDO = ValueCopyUtils.copyProperties(nodeDTO, new NodeDO());
        nodePartyDO.setId(null);
        nodePartyDO.setNodeId(null);
        nodeDTO.setUpdateTime(new Date());
        return nodeManager.update(nodePartyDO, uw -> uw.eq(NodeDO::getEnabled, true)
                .eq(NodeDO::getNodeId, nodeDTO.getNodeId())
        );
    }

    public CommonResult<Object> updateNodeState(NodeDTO nodeDTO, NodeState toState) throws LogicException {
        return stateEngine.updateState(nodeManager.getBaseMapper(), toState
                , NodeDO::getState
                , NodeState::name
                , uw -> uw.set(NodeDO::getUpdateTime, new Date())
                        .eq(NodeDO::getNodeId, nodeDTO.getNodeId())
        );
    }

    public boolean isCanDeleteNodeState(NodeState state) {
        return stateEngine.isCanDeleteNodeState(state);
    }

    public NodeState queryNodeState(String nodeId) {
        NodeDTO nodeDTO = this.queryByNodeId(nodeId);
        return nodeDTO != null ? nodeDTO.getState() : null;
    }

    public NodeDTO queryByNodeId(String nodeId) {
        final NodeDO nodeDO = nodeManager.queryOne(uw -> uw.eq(NodeDO::getNodeId, nodeId));
        return nodeDO != null ? ValueCopyUtils.copyProperties(nodeDO, new NodeDTO()) : null;
    }

    public NodeDTO queryNodeDetails(String nodeId) {
        if (myNodeInfo.getNodeId().equals(nodeId)) {
            return this.myNodeDTO();
        }

        // query info
        NodeDTO nodeDTO = queryByNodeId(nodeId);
        if (Objects.isNull(nodeDTO)) return null;
        // query route
        List<RouteDTO> routeDTOList = commonRouteManager.queryByNodeId(nodeId);
        nodeDTO.setRouteDTOList(routeDTOList);
        return nodeDTO;
    }

    @SuppressWarnings("unchecked")
    public Page<NodeDTO> queryPage(NodePageDTO pageDTO) {
        // paging query
        final SFunction<NodeDO, Object> orderFields = pageDTO.getOrderFields();
        final Page<NodeDO> nodePartyDOPage = nodeManager.page(
                new Page<>(pageDTO.getPageNum(), pageDTO.getPageSize())
                , new QueryWrapper<NodeDO>().lambda()
                        .eq(Objects.nonNull(pageDTO.getNodeId()), NodeDO::getNodeId, pageDTO.getNodeId())
                        .like(Objects.nonNull(pageDTO.getNodeName()), NodeDO::getNodeName, pageDTO.getNodeName())
                        .eq(Objects.nonNull(pageDTO.getOwner()), NodeDO::getOwner, pageDTO.getOwner())
                        .eq(Objects.nonNull(pageDTO.getState()), NodeDO::getState, pageDTO.getState())
                        .orderByAsc(pageDTO.getOrderRule(), orderFields)
                        .orderByDesc(!pageDTO.getOrderRule(), orderFields)
        );

        final IPage<NodeDTO> nodeDTOIPage = nodePartyDOPage.convert(nodePartyDO ->
                ValueCopyUtils.copyProperties(nodePartyDO, new NodeDTO())
        );

        // query node route
        for (NodeDTO nodeDTO : nodeDTOIPage.getRecords()) {
            final List<RouteDTO> routeDOList = commonRouteManager.queryByNodeId(nodeDTO.getNodeId());
            nodeDTO.setRouteDTOList(routeDOList);
        }

        return ((Page<NodeDTO>) nodeDTOIPage);
    }

    @Override
    public void unionPipelineRegisterEvent(UnionPipelineRegister register) {
        // add node and node route
        register.registration(ADD_NODE_AND_ROUTE, (dto, unionPipeline) -> unionPipeline
                .addNext(CommonNodeManager.class, CommonNodeManager::addNode)
                .addNextS(CommonRouteManager.class, mapper -> mapper.addRoute(dto.getRouteDTOList())));

        // delete node and node route
        register.registration(DELETE_NODE_AND_ROUTE, (dto, unionPipeline) -> unionPipeline
                .addNext(CommonNodeManager.class, CommonNodeManager::deleteByNodeId)
                .castResult(NodeDTO::getNodeId)
                .addNext(CommonRouteManager.class, CommonRouteManager::deleteRoute));

        // update node and node route
        register.registration(UPDATE_NODE_AND_ROUTE, (dto, unionPipeline) -> unionPipeline
                .addNext(CommonNodeManager.class, CommonNodeManager::updateNodeInfo)
                .assertResult(Boolean::booleanValue, new LogicException(MessageCodeEnum.DATA_UPDATE_ERROR, "update node failed"))
                .addNextS(CommonRouteManager.class, mapper -> mapper.mergeRoute(dto, dto.getNodeId()))
        );
    }
}
